﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using SoftwareConsulting.Interfaces.BasicEntities;
using SoftwareConsulting.BI11.Studio.InternalServices;

[assembly: CLSCompliant(true)]
namespace SoftwareConsulting.BI11.Studio.BasicEntities
{    
    /// <summary>
    /// Класс иерархии
    /// </summary>   
    [Serializable]
    public class Hierarchy<T> : UniqueChild, IHierarchy<T> where T:IUniqueChild
    {
        /// <summary>
        /// Класс словаря с преобладанием значений
        /// </summary>
        [Serializable]
        private sealed class ValuePredominanceDictionary<T2> : Dictionary<Guid, T2>, IEnumerable<T2> where T2 : IUnique
        {
            private object _synchronizer = new object();

            public new IEnumerator<T2> GetEnumerator()
            {
                lock (_synchronizer)
                {
                    foreach (T2 val in Values)
                    {
                        yield return val;
                    }
                }
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            private ValuePredominanceDictionary(SerializationInfo info, StreamingContext context) :
                base(info, context)
            {
            }

            public ValuePredominanceDictionary() :
                base()
            {
            }
        }  

        [NonSerialized]private bool _isBlocked; //флаг блокировки иерархии от изменений
        private ValuePredominanceDictionary<T> _items; //элементы иерархии
        private ValuePredominanceDictionary<PositionOfUnique> _positions; //позиции элементов иерархии        

        /// <summary>
        /// Инициализирует класс
        /// </summary>        
        private void InitializeClass()
        {                        
            _isBlocked = false;
            if (_items == null)
            {
                _items = new ValuePredominanceDictionary<T>();
            }
            if (_positions == null)
            {
                _positions = new ValuePredominanceDictionary<PositionOfUnique>();
            }
        }

        [OnDeserializedAttribute()]
        private void WhenDeserialized(StreamingContext context)
        {
            InitializeClass();
        }

        public IEnumerator<T> GetEnumerator()
        {            
            return _items.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }        

        /// <summary>
        /// Возвращает количество листьев (концевых вершин) в иерархии
        /// </summary>
        public int LeafsCount
        {
            get
            {
                int c = ItemsCount;
                int dlc = 0;                
                for (int i = 0; i < c; i++)
                {
                    IHierarchy<T> h = GetItemByPosition(i) as IHierarchy<T>;
                    if (h != null)
                    {
                        dlc += h.LeafsCount;
                    }                    
                }
                if (c > 0)
                {
                    return dlc;
                }
                return 1;
            }
        }

        /// <summary>
        /// Возвращает максимальную глубину иерархии
        /// </summary>        
        /// <remarks>нумерация с самой иерархии (нулевой уровень)</remarks>
        public int DeepestLevel
        {
            get
            {
                int c = ItemsCount;
                int mostdeep = -1;
                for (int i = 0; i < c; i++)
                {
                    IHierarchy<T> h = GetItemByPosition(i) as IHierarchy<T>;
                    if (h != null)
                    {
                        int curdeep = h.DeepestLevel;
                        if (curdeep > mostdeep)
                        {
                            mostdeep = curdeep;
                        }
                    }
                }
                return mostdeep + 1;
            }
        }

        /// <summary>
        /// Возвращает первый встреченный элемент по его тэгу (рекурсивно)
        /// </summary>
        /// <param name="needTag"></param>
        /// <returns></returns>
        public T GetFirstItemByTag(object needTag)
        {
            foreach (T curItem in this)
            {                
                if (curItem.Tag.Equals(needTag))
                {
                    return curItem;
                }
                var curcon = curItem as IHierarchy<T>;
                if (curcon != null)
                {
                    T inner = curcon.GetFirstItemByTag(needTag);
                    if (inner != null)
                        return inner;
                }
            }
            return default(T);
        }

        /// <summary>
        /// Возвращает флаг блокировки иерархии
        /// </summary>
        public bool IsBlocked
        {
            get
            {
                return _isBlocked;
            }
        }        
             
        /// <summary>
        /// Возвращает глубину элемента
        /// </summary>        
        public int Deep
        {
            get
            {
                var par = Parent as IHierarchy<T>;
                if (par != null)                
                {
                    return par.Deep + 1;
                }
                return 0;
            }
        }

        /// <summary>
        /// Возвращает идентификаторы всех элементов иерархии, включая саму иерархию
        /// </summary>        
        public List<Guid> IdsIncludingHierarchy
        {
            get
            {
                List<Guid> res = new List<Guid>();
                res.Add(Id);
                foreach (T val in this)
                {
                    var cur = val as IHierarchy<T>;
                    if (cur != null)                    
                    {                    
                        res.AddRange(cur.Ids);
                    }
                }                
                return res;
            }
        }

        /// <summary>
        /// Возвращает идентификаторы всех элементов иерархии
        /// </summary>        
        public List<Guid> Ids
        {
            get
            {
                List<Guid> res = new List<Guid>();                
                foreach(T val in this)
                {
                    var cur = val as IHierarchy<T>;
                    if (cur != null)                    
                    {
                        res.AddRange(cur.IdsIncludingHierarchy);
                    }
                }
                return res;
            }
        }

        /// <summary>
        /// Удаляет из иерархии все ее элементы
        /// </summary>
        virtual public void Clear()
        {
            if (IsBlocked)
            {
                throw new BIHierarchyBlockedException(null);
            }
            _items.Clear();
            _positions.Clear();            
        }

        /// <summary>
        /// Возвращает флаг того, содержит ли данная иерархия указанный элемент (на верхнем уровне)
        /// </summary>
        /// <param name="needItem"></param>
        /// <returns></returns>
        public bool Contains(T needItem)
        {
            return _items.ContainsValue(needItem);
        }

        /// <summary>
        /// Возвращает флаг того, содержит ли данная иерархия указанный элемент с учетом флага рекурсивности
        /// </summary>
        /// <param name="needItem"></param>
        /// <returns></returns>
        public bool Contains(T needItem, bool isRecursively)
        {
            bool b = Contains(needItem);
            if (b || (!isRecursively))
            {
                return b;
            }
            foreach (T item in _items)
            {
                if ((item is IHierarchy<T>) && ((item as IHierarchy<T>).Contains(needItem, true)))
                {
                    return true;
                }
            }            
            return false;
        }

        /// <summary>
        /// Возвращает флаг того, содержит ли данная иерархия указанный элемент (по идентификатору)
        /// </summary>
        /// <param name="needItemId"></param>
        /// <returns></returns>
        public bool ContainsId(Guid needItemId)
        {            
            return _items.ContainsKey(needItemId);
        }        

        /// <summary>
        /// Возвращает количество элементов иерархии
        /// </summary>        
        public int ItemsCount
        {
            get
            {
                return _items.Count;
            }
        }        

        /// <summary>
        /// Возвращает элемент по его идентификатору с учетом флага рекурсивности
        /// </summary>
        /// <param name="needId"></param>
        /// <returns></returns>
        public T GetItemById(Guid needId, bool recursively)
        {
            if (!recursively)
            {
                T result = default(T);
                _items.TryGetValue(needId, out result);
                return result;
            }            
            foreach (T curItem in _items)
            {                             
                if (curItem.Id.Equals(needId))
                {
                    return curItem;                    
                }
                var curcon = curItem as IHierarchy<T>;
                if (curcon != null)                
                {
                    T inner = curcon.GetItemById(needId, true);
                    if (inner != null)
                        return inner;
                }             
            }
            return default(T);//не знаю че за хрень, просто компилятор сказал использовать это вместо Null
        }

        /// <summary>
        /// Возвращает элемент по его идентификатору (нерекурсивно)
        /// </summary>
        /// <param name="needId"></param>
        /// <returns></returns>
        public T GetItemById(Guid needId)
        {
            T result = default(T);
            _items.TryGetValue(needId, out result);
            return result;
        }

        /// <summary>
        /// Возвращает элемент по его идентификатору (нерекурсивно)
        /// </summary>
        /// <param name="needId"></param>
        /// <returns></returns>        
        public T this[Guid needId]
        {
            get
            {
                return GetItemById(needId);
            }           
        }
        
        /// <summary>
        /// Добавляет новый элемент в конец
        /// </summary>
        /// <param name="newItem"></param>
        /// <returns></returns>
        virtual public int Add(T newItem)
        {
            if (IsBlocked)
            {
                throw new BIHierarchyBlockedException(null);
            }
            _items.Add(newItem.Id, newItem);
            if (newItem.Parent == null)
            {
                newItem.Parent = this;
            }
            _positions.Add(newItem.Id, new PositionOfUnique(newItem.Id, _items.Count - 1));
            return ItemsCount;
        }                                                      

        /// <summary>
        /// Возвращает следующий элемент без учета иерархии
        /// </summary>        
        public T NextSiblingItem
        {
            get
            {
                if (ItemsCount > 0)
                {
                    return GetItemByPosition(0);
                }
                IHierarchy<T> par = Parent as IHierarchy<T>;
                IHierarchy<T> oldpar = this as IHierarchy<T>;
                while (par != null)
                {
                    T me = par.GetItemById(oldpar.Id);
                    int index = par.GetPositionOfItem(me);
                    if (index != par.ItemsCount - 1)
                    {
                        return par.GetItemByPosition(index + 1);
                    }
                    oldpar = par;
                    par = par.Parent as IHierarchy<T>;
                    if (par != null)
                    {
                        int oldparindex = par.GetPositionOfItem((T)oldpar);
                        if (oldparindex != par.ItemsCount - 1)
                        {
                            return par.GetItemByPosition(oldparindex + 1);
                        }
                    }
                }                
                return default(T);
            }
        }                                
        
        /// <summary>
        /// Удаляет указанный элемент
        /// </summary>
        /// <param name="itemToDelete"></param>
        /// <returns></returns>
        virtual public int Delete(T itemToDelete)
        {
            if (IsBlocked)
            {
                throw new BIHierarchyBlockedException(null);
            }
            Assert.FailIf(!_items.ContainsValue(itemToDelete), Assert.Id.Assert00011);            
            int pos = GetPositionOfItem(itemToDelete);
            _items.Remove(itemToDelete.Id);
            _positions.Remove(itemToDelete.Id);            
            foreach(PositionOfUnique posit in _positions)            
            {                
                PositionOfUnique poss = posit as PositionOfUnique;
                if (poss.Position > pos)
                {
                    poss.Position = poss.Position - 1;
                }
            }
            return ItemsCount;
        }

        /// <summary>
        /// Удаляет элемент по его идентификатору
        /// </summary>
        /// <param name="needId"></param>
        /// <returns></returns>
        public int DeleteById(Guid needId)
        {
            if (IsBlocked)
            {
                throw new BIHierarchyBlockedException(null);
            }
            T item = GetItemById(needId, true);
            return Delete(item);
        }        

        /// <summary>
        /// Возвращает позицию элемента в иерархии
        /// </summary>
        /// <param name="needItem"></param>
        /// <returns></returns>
        public int GetPositionOfItem(T needItem)
        {
            return (_positions[needItem.Id] as PositionOfUnique).Position;
        }                

        /// <summary>
        /// Возвращает элемент по его позиции (на текущем уровне иерархии)
        /// </summary>
        /// <param name="needPosition"></param>
        /// <returns></returns>
        public T GetItemByPosition(int needPosition)
        {
            Assert.FailIf(needPosition >= ItemsCount, Assert.Id.Assert00015);
            foreach (PositionOfUnique pos in _positions)
            {
                if (pos.Position == needPosition)
                {
                    return GetItemById(pos.Id);
                }
            }
            Assert.Fail(Assert.Id.Assert00016);
            return default(T);
        }

        /// <summary>
        /// Возвращает предыдущий элемент без учета иерархии
        /// </summary>       
        public T PreviousSiblingItem
        {
            get
            {
                IHierarchy<T> par = Parent as IHierarchy<T>;
                if (par == null)
                {
                    return default(T);
                }
                T me = par.GetItemById(Id);
                int pos = par.GetPositionOfItem(me);
                if (pos == 0)
                {
                    return (T)par.Parent;
                }
                return par.GetItemByPosition(pos - 1);
            }
        }

        /// <summary>
        /// Возвращает глобальную позицию элемента (во всей иерархии)
        /// </summary>
        public int GetGlobalPositionOfItem(T needItem)
        {
            T prev = (T)((needItem as IHierarchy<T>).PreviousSiblingItem);
            if (prev == null)
            {
                return 0;
            }            
            IHierarchy<T> par = prev.Parent as IHierarchy<T>;
            if (par != null)
            {
                return par.GetGlobalPositionOfItem(prev) + 1;
            }
            return 1;
        }

        /// <summary>
        /// Возвращает элемент по его позиции (с учетом флага глобальности иерархии)
        /// </summary>
        /// <param name="needPosition"></param>
        public T GetItemByPosition(int needPosition, bool isGlobalPosition)
        {            
            if (!isGlobalPosition)
            {
                return GetItemByPosition(needPosition);
            }
            Assert.FailIf(needPosition < 0, Assert.Id.Assert00070); 
            T item = GetItemByPosition(0);
            IHierarchy<T> it = item as IHierarchy<T>;
            while (it != null)
            {
                if (GetGlobalPositionOfItem((T)it) == needPosition)
                {
                    return (T)it;
                }                
                it = it.NextSiblingItem as IHierarchy<T>;
            }
            Assert.Fail(Assert.Id.Assert00189);
            return default(T);
        }

        /// <summary>
        /// Вставляет новый элемент в указанную позицию
        /// </summary>
        /// <param name="newItem"></param>
        /// <param name="needPosition"></param>
        /// <returns>количество элементов в иерархии после вставки</returns>
        virtual public int Insert(T newItem, int needPosition)
        {
            if (IsBlocked)
            {
                throw new BIHierarchyBlockedException(null);
            }
            Assert.FailIf(needPosition < 0, Assert.Id.Assert00188);            
            needPosition = needPosition <= ItemsCount ? needPosition : ItemsCount;
            _items.Add(newItem.Id, newItem);
            foreach(PositionOfUnique poss in _positions)
            {                                
                if (poss.Position >= needPosition)
                {
                    poss.Position = poss.Position + 1;
                }
            }
            _positions.Add(newItem.Id, new PositionOfUnique(newItem.Id, needPosition));
            if (newItem.Parent == null)
            {
                newItem.Parent = this;
            }            
            return ItemsCount;
        }

        /// <summary>
        /// Удаляет элемент по его позиции
        /// </summary>
        /// <param name="needPosition"></param>
        /// <returns></returns>
        virtual public int DeleteByPosition(int needPosition)
        {
            if (IsBlocked)
            {
                throw new BIHierarchyBlockedException(null);
            }
            T item = GetItemByPosition(needPosition);            
            return Delete(item);            
        }

        /// <summary>
        /// Меняет местами 2 элемента иерархии
        /// </summary>
        /// <param name="item1">элемент 1</param>
        /// <param name="item2">элемент 2</param>
        virtual public void SwapItems(T item1, T item2)
        {
            if (IsBlocked)
            {
                throw new BIHierarchyBlockedException(null);
            }
            int mempos = (_positions[item1.Id] as PositionOfUnique).Position;
            (_positions[item1.Id] as PositionOfUnique).Position = (_positions[item2.Id] as PositionOfUnique).Position;
            (_positions[item2.Id] as PositionOfUnique).Position = mempos;
        }

        /// <summary>
        /// Замена элемента в указанной позиции на новый элемент
        /// </summary>
        /// <param name="newItem"></param>
        /// <param name="needPosition"></param>
        public void ChangeItemAtPosition(T newItem, int needPosition)
        {
            if (IsBlocked)
            {
                throw new BIHierarchyBlockedException(null);
            }
            DeleteByPosition(needPosition);
            Insert(newItem, needPosition);
        }

        /// <summary>
        /// Поднимает элемент иерархии выше на одну позицию
        /// </summary>
        /// <param name="needItem"></param>
        /// <returns>true - после поднятия еще есть куда двигаться, false - иначе</returns>
        virtual public bool UpItem(int curItemPosition)
        {
            if (IsBlocked)
            {
                throw new BIHierarchyBlockedException(null);
            }
            SwapItems(GetItemByPosition(curItemPosition), GetItemByPosition(curItemPosition - 1));
            return curItemPosition > 0;
        }

        /// <summary>
        /// Опускает элемент иерархии ниже на одну позицию
        /// </summary>
        /// <param name="needItem"></param>
        /// <returns>true - после опускания еще есть куда двигаться, false - иначе</returns>
        virtual public bool DownItem(int curItemPosition)
        {
            if (IsBlocked)
            {
                throw new BIHierarchyBlockedException(null);
            }
            SwapItems(GetItemByPosition(curItemPosition), GetItemByPosition(curItemPosition + 1));
            return curItemPosition < (ItemsCount - 1);
        }

        /// <summary>
        /// Блокирует иерархию от изменений
        /// </summary>
        /// <remarks>снять блокировку нельзя</remarks>
        public void Block()
        {
            _isBlocked = true;
        }        

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="owner"></param>
        public Hierarchy(IHierarchy<T> owner):base(owner)
        {
            InitializeClass();
        }        

        /// <summary>
        /// Конструктор класса по умолчанию
        /// </summary>
        /// <param name="Owner"></param>
        public Hierarchy():base()
        {
            InitializeClass();
        }
    }
}
